package nodes;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
import java.util.TreeMap;

public class NodeContainer extends TreeMap<String, Node> {
	private static final long serialVersionUID = 1L;
	 
	
	/**
	 * Add a node that has already been created. Don't add it's children.
	 * 
	 * @param node		A node that has already been created and whos children have already been checked for consistency
	 */
	public void addNode(Node node) {
		put(node.getIP(), node);
	}
	
	/**
	 * Create a node, add it to the current container, and add it's peer and leaves and if necessary.
	 * 
	 * @param IP			This is a string in the form XXX.XXX.XXX.XXX
	 * @param type			This is a variable to define the type of node
	 * @param peerConnect	List of connected peers
	 * @param leafConnect	List of the connected leaves
	 */
	public void addNode(String IP, int type, ArrayList<String> peerConnect, ArrayList<String> leafConnect){
		//Check to see if we already have this node
		if(containsIP(IP)){
			Node currNode = getNode(IP);											//If so, get the node
			currNode.setType(type);													//Set its type
			checkPeerConnections(currNode, peerConnect);							//Check double-link consistency with peer nodes
			checkLeafConnections(currNode, leafConnect, Node.NODE_TYPE_LEAF);		//Check double-link consistency with leaf nodes
		} //if
		
		//Else
		else {
			Node newNode = new Node(IP, type);										//Create the new node
			put(IP, newNode);														//Add it to the treemap
			establishPeerConnections(newNode, peerConnect);							//Establish double-link consistency with peer connections
			establishLeafConnections(newNode, leafConnect, Node.NODE_TYPE_LEAF);	//Establish double-link consistency with leaf connections
		}//else
	}//addNode()
	
	
	
	/**
	 * @param currNode		The current node being added to the nodeContainer by the parser
	 * @param connections	A list of the nodes connected to currNode
	 * @param type			Variable to define the type of node
	 */
	private void checkLeafConnections(Node currNode, ArrayList<String> connections, int type){
		for(int index = 0; index < connections.size(); index++){		//Loop through its connections and check for consistency
			
			String connectIP = connections.get(index);					//Establish the IP for the connected node
			
			if(containsIP(connectIP)){									//If we already have the connected node
				Node connectNode = getNode(connectIP);					//Get it
				
				if(!connectNode.containsNode(currNode)){				//If the connected node doesn't know about us,
					connectNode.addConnection(currNode);				//Add our new node to its list of connections
					currNode.addConnection(connectNode);				//If he didn't know about us, then couldn't have added us to his list already
				}//if
			}//if
			
			else{														//If the connected Node doesn't exist
				Node connectNode = new Node(connectIP, type);			//Create it as type unknown
				put((connectIP), connectNode);							//Add it to the treemap
				connectNode.addConnection(currNode);					//Add ourselves to its connection list
				currNode.addConnection(connectNode);					//Add the newly created connectNode to our list
			}//else
		}//for
	}//checkConnections
	
	
	/**
	 * @param currNode		The node currently being added by the parser
	 * @param connections	A list of nodes the parser is connected to, may subsiquently be added if they don't already exist
	 * @param type			The type of nodes in the ArrayList
	 */
	private void establishLeafConnections(Node currNode, ArrayList<String> connections, int type){
		for(int index = 0; index < connections.size(); index++){		//Loop through its connections and check for consistency
			
			String connectIP = connections.get(index);					//Establish the IP for the connected node
			
			if(containsIP(connectIP)){									//If we already have the connected node
				Node connectNode = getNode(connectIP);					//Get it
				
				connectNode.addConnection(currNode);					//Add us to its list of connections
				currNode.addConnection(connectNode);					//Add him to our list of connections
			}//if
			
			else{														//If the connected Node doesn't exist
				Node connectNode = new Node(connectIP, type);			//Create it as type unknown
				put((connectIP), connectNode);
				connectNode.addConnection(currNode);					//Add ourselves to its connection list
				currNode.addConnection(connectNode);					//Add the newly created connectNode to currNodes list
			}//else
		}//for
	}//checkConnections
	
	/**
	 * @param currNode		The current node being added to the nodeContainer by the parser
	 * @param connections	A list of the nodes connected to currNode
	 */
	private void checkPeerConnections(Node currNode, ArrayList<String> connections){
		for(int index = 0; index < connections.size(); index++){		//Loop through its connections and check for consistency
			
			String connectIP = connections.get(index);					//Establish the IP for the connected node
			
			if(containsIP(connectIP)){									//If we already have the connected node
				Node connectNode = getNode(connectIP);					//Get it
				
				if(!connectNode.containsNode(currNode)){				//If the connected node doesn't know about us
					connectNode.addConnection(currNode);				//Add our new node to its list of connections
					currNode.addConnection(connectNode);				//If he didn't know about us, then he couldn't have already added us to his list
				}//if
			}//if
		}//for
	}//checkConnections
	
	
	/**
	 * @param currNode		The current node being added to the nodeContainer by the parser
	 * @param connections	A list of the nodes connected to the currNode
	 */
	private void establishPeerConnections(Node currNode, ArrayList<String> connections){
		for(int index = 0; index < connections.size(); index++){		//Loop through its connections and check for consistency
			
			String connectIP = connections.get(index);					//Establish the IP for the connected node
			
			if(containsIP(connectIP)){									//If we already have the connected node
				Node connectNode = getNode(connectIP);					//Get the connected Node
				
				connectNode.addConnection(currNode);					//Add our new node to its list of connections				
				currNode.addConnection(connectNode);					//Add connect Node to our current nodes list
			}//if
		}//for
	}//checkConnections
	
	
	
	/**
	 * @param IP	This is a string in the form XXX.XXX.XXX.XXX
	 */
	public void removeNode(String IP){
		remove(IP);
	}//removeNode
	
	
	/**
	 * @param IP	This is a string in the form XXX.XXX.XXX.XXX
	 * @return		The node referenced by IP
	 */
	public Node getNode(String IP){
		return get(IP);
	}
	
	public int getSize()
	{
		return size();
	}
	
	/**
	 * @return 		A Collection of all nodes in the container
	 */
	public Collection<Node> getAllNodes()
	{
		return values();
	}
	
	
	/**
	 * @param IP	This is a string in the form XXX.XXX.XXX.XXX
	 * @return 		true if the specified IP address is contained in the 
	 */
	public boolean containsIP(String IP){ 
		return containsKey(IP); 
	}//containsIP()
	
	
	public Node getRandomNode(int type) {
		if(type == 0)
			type = Node.NODE_TYPE_UNKNOWN;
		
		// Get the iterator we will use to cycle through the objects
		Iterator<Node> iter = this.values().iterator();
		
		// Loop a random number of times until we find a node of the specified type
		Random randObj = new Random();
		int r = randObj.nextInt(this.values().size());
		Node lastNodeFound = null;
		for(int i = 0; i < r; i++) {
			Node curNode = (Node)iter.next();
			if(type == Node.NODE_TYPE_UNKNOWN || curNode.getType() == type) {
				lastNodeFound = curNode;
			}
		}
		
		return lastNodeFound;
	}
	
//	
//	/**
//	 * Converts a String IP address into a Long which will be used as the key value for the TreeMap
//	 * 
//	 * @param IP		This is a string in the form XXX.XXX.XXX.XXX
//	 * @return Total	Returns the integer key.	
//	 */
//	private static long encodeIP(String IP){
//		String[] s = IP.split("\\.");
//		
//		long total = (long)Integer.parseInt(s[3]) + (long)Integer.parseInt(s[2])*255 + (long)Integer.parseInt(s[1])*(long)65025 +(long)Integer.parseInt(s[0])*(long)16581375; 
//		Long Total = new Long(total);
//		return Total;
//	}//encodeIP()

}
